/*
 * Copyright (C) 2009, Edmundo Albuquerque de Souza e Silva.
 *
 * This file may be distributed under the terms of the Q Public License
 * as defined by Trolltech AS of Norway and appearing in the file
 * LICENSE.QPL included in the packaging of this file.
 *
 * THIS FILE IS PROVIDED AS IS WITH NO WARRANTY OF ANY KIND, INCLUDING
 * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */

package javax.media.format;

import javax.media.Format;

import net.sf.fmj.utility.FormatUtils;

/**
 * Coding complete.
 * @author Ken Larson
 *
 */
public class AudioFormat extends Format
{
	public static final int BIG_ENDIAN = 1;
	public static final int LITTLE_ENDIAN = 0;

	public static final int SIGNED = 1;
	public static final int UNSIGNED = 0;

	protected double sampleRate = NOT_SPECIFIED;

	protected int sampleSizeInBits = NOT_SPECIFIED;

	protected int channels = NOT_SPECIFIED;

	protected int endian = NOT_SPECIFIED;

	protected int signed = NOT_SPECIFIED;

	protected double frameRate = NOT_SPECIFIED;

	protected int frameSizeInBits = NOT_SPECIFIED;
	
	public static final String LINEAR = "LINEAR";
	public static final String ULAW = "ULAW";
	public static final String ULAW_RTP = "ULAW/rtp";
	public static final String ALAW = "alaw";	// strange that this is lower case and ULAW is not...
	public static final String IMA4 = "ima4";
	public static final String IMA4_MS = "ima4/ms";
	public static final String MSADPCM = "msadpcm";
	public static final String DVI = "dvi";
	public static final String DVI_RTP = "dvi/rtp";
	public static final String G723 = "g723";
	public static final String G723_RTP = "g723/rtp";
	public static final String G728 = "g728";
	public static final String G728_RTP = "g728/rtp";
	public static final String G729 = "g729";
	public static final String G729_RTP = "g729/rtp";
	public static final String G729A = "g729a";
	public static final String G729A_RTP = "g729a/rtp";
	public static final String GSM = "gsm";
	public static final String GSM_MS = "gsm/ms";
	public static final String GSM_RTP = "gsm/rtp";
	public static final String MAC3 = "MAC3";
	public static final String MAC6 = "MAC6";
	public static final String TRUESPEECH = "truespeech";
	public static final String MSNAUDIO = "msnaudio";
	public static final String MPEGLAYER3 = "mpeglayer3";
	public static final String VOXWAREAC8 = "voxwareac8";
	public static final String VOXWAREAC10 = "voxwareac10";
	public static final String VOXWAREAC16 = "voxwareac16";
	public static final String VOXWAREAC20 = "voxwareac20";
	public static final String VOXWAREMETAVOICE = "voxwaremetavoice";
	public static final String VOXWAREMETASOUND = "voxwaremetasound";
	public static final String VOXWARERT29H = "voxwarert29h";
	public static final String VOXWAREVR12 = "voxwarevr12";
	public static final String VOXWAREVR18 = "voxwarevr18";
	public static final String VOXWARETQ40 = "voxwaretq40";
	public static final String VOXWARETQ60 = "voxwaretq60";
	public static final String MSRT24 = "msrt24";
	public static final String MPEG = "mpegaudio";
	public static final String MPEG_RTP = "mpegaudio/rtp";
	public static final String DOLBYAC3 = "dolbyac3";


	// TODO: these are not used in this impl, only added for serialization compatibility.
	// TODO: probably used for computeDuration, to make it faster.
	double multiplier = -1.0;
	int margin = 0;
	boolean init = false;




	public AudioFormat(String encoding)
	{
		super(encoding);
		this.dataType = byteArray;
	}

	public AudioFormat(String encoding, double sampleRate,
			int sampleSizeInBits, int channels)
	{
		super(encoding); 
		this.dataType = byteArray;
		this.sampleRate = sampleRate;
		this.sampleSizeInBits = sampleSizeInBits;
		this.channels = channels;
	}

	public AudioFormat(String encoding, double sampleRate,
			int sampleSizeInBits, int channels, int endian, int signed)
	{
		super(encoding); 
		this.dataType = byteArray;
		this.sampleRate = sampleRate;
		this.sampleSizeInBits = sampleSizeInBits;
		this.channels = channels;
		this.endian = endian;
		this.signed = signed;
		
	}

	public AudioFormat(String encoding, double sampleRate,
			int sampleSizeInBits, int channels, int endian, int signed,
			int frameSizeInBits, double frameRate, Class dataType)
	{
		super(encoding, dataType);
		this.sampleRate = sampleRate;
		this.sampleSizeInBits = sampleSizeInBits;
		this.channels = channels;
		this.endian = endian;
		this.signed = signed;
		this.frameSizeInBits = frameSizeInBits;
		this.frameRate = frameRate;
	}

	
	public double getSampleRate()
	{
		return sampleRate;
	}

	public int getSampleSizeInBits()
	{
		return sampleSizeInBits;
	}
	
	public int getChannels()
	{
		return channels;
	}

	public int getEndian()
	{
		return endian;
	}

	public int getSigned()
	{
		return signed;
	}

	public int getFrameSizeInBits()
	{
		return frameSizeInBits;
	}


	public double getFrameRate()
	{
		return frameRate;
	}

	


	public long computeDuration(long length)
	{	
		// TODO: this calculation does not always give the exact same result as the reference implementation.
		// see JavaSound API for information on frames and samples.
		// http://java.sun.com/j2se/1.4.2/docs/api/javax/sound/sampled/AudioFormat.html
		
		final double frameRate;
		final int frameSizeInBits; 
		
		if (FormatUtils.specified(this.frameRate) && FormatUtils.specified(this.frameSizeInBits))
		{
			frameRate = this.frameRate;
			frameSizeInBits = this.frameSizeInBits;
		}
		else if (FormatUtils.specified(this.sampleRate) && FormatUtils.specified(this.sampleSizeInBits) && FormatUtils.specified(this.channels))
		{
//			this calculation is correct for uncompressed data.  TODO: are there any formats where this is not correct?
			frameRate = this.sampleRate;
			frameSizeInBits = this.sampleSizeInBits * this.channels;
		}
		else
			return -1L;
		
		return 1000L * (long)(length * 8 * 1000000.0 / ((double) frameRate * (double) frameSizeInBits));
	}

	public String toString()
	{	
// examples:		
//		dolbyac3, Unknown Sample Rate
//		dolbyac3, 2.0 Hz, 1-bit, Stereo, Unsigned, 6.0 frame rate, FrameSize=5 bits
//		dolbyac3, 2.0 Hz, 1-bit, 0-channel, Unsigned, 6.0 frame rate, FrameSize=5 bits
//		dolbyac3, 2.0 Hz, 1-bit, 3-channel, Unsigned, 6.0 frame rate, FrameSize=5 bits
		
		// TODO: use "KHz" when appropriate.

		final StringBuffer b = new StringBuffer();
		b.append(encoding);
		
		b.append(", ");
		if (FormatUtils.specified(sampleRate))
			b.append(sampleRate + " Hz");
		else
			b.append("Unknown Sample Rate");
		
		if (FormatUtils.specified(sampleSizeInBits))
		{
			b.append(", ");
			b.append("" + sampleSizeInBits + "-bit");
		}
		if (FormatUtils.specified(channels))
		{
			b.append(", ");
			if (channels == 1)
				b.append("Mono");
			else if (channels == 2)
				b.append("Stereo");
			else
				b.append("" + channels + "-channel");
		}
		
		if (FormatUtils.specified(endian) && FormatUtils.specified(sampleSizeInBits) && sampleSizeInBits > 8)
		{
			
			if (endian == BIG_ENDIAN)
			{	b.append(", ");
				b.append("BigEndian");
			}
			else if (endian == LITTLE_ENDIAN)
			{	
				b.append(", ");
				b.append("LittleEndian");
			}
			else 
			{	// unknown, don't append anything
			}
		}
		
		if (FormatUtils.specified(signed))
		{
			b.append(", ");
			if (signed != SIGNED)
				b.append("Unsigned");
			else
				b.append("Signed");
		}
		
		if (FormatUtils.specified(frameRate))
		{
			b.append(", ");
			b.append("" + frameRate + " frame rate");
		}
		if (FormatUtils.specified(frameSizeInBits))
		{
			b.append(", FrameSize=" + frameSizeInBits + " bits"); 
		}
		return b.toString();
		
	}

	public boolean equals(Object format)
	{	
		if (!super.equals(format))
			return false;
		
		if (!(format instanceof AudioFormat))	
		{
			return false;
		}
		
		final AudioFormat oCast = (AudioFormat) format;
		return 
			this.sampleRate == oCast.sampleRate &&
			this.sampleSizeInBits == oCast.sampleSizeInBits &&
			this.channels == oCast.channels &&
			this.endian == oCast.endian &&
			this.signed == oCast.signed &&
			this.frameSizeInBits == oCast.frameSizeInBits &&
			this.frameRate == oCast.frameRate;
	}


	public boolean matches(Format format)
	{	
		if (!super.matches(format))
		{	
			if (getClass() == FormatUtils.audioFormatClass) FormatUtils.traceMatches(this, format, false); // otherwise let subclass trace
			return false;
		}
		
		if (!(format instanceof AudioFormat))
		{	
			final boolean result = true;
			if (getClass() == FormatUtils.audioFormatClass) FormatUtils.traceMatches(this, format, result);
			return result;
		}
		
		final AudioFormat oCast = (AudioFormat) format;

		final boolean result = 
			FormatUtils.matches(this.sampleRate, oCast.sampleRate) &&
			FormatUtils.matches(this.sampleSizeInBits, oCast.sampleSizeInBits) &&
			FormatUtils.matches(this.channels, oCast.channels) &&
			FormatUtils.matches(this.endian, oCast.endian) &&
			FormatUtils.matches(this.signed, oCast.signed) &&
			FormatUtils.matches(this.frameSizeInBits, oCast.frameSizeInBits) &&
			FormatUtils.matches(this.frameRate, oCast.frameRate);
		
		if (getClass() == FormatUtils.audioFormatClass) FormatUtils.traceMatches(this, format, result); // otherwise let subclass trace
		
		return result;

	}



	public Format intersects(Format other)
	{	
		
		final Format result = super.intersects(other);

		if (other instanceof AudioFormat)
		{
			final AudioFormat resultCast = (AudioFormat) result;
			
			final AudioFormat oCast = (AudioFormat) other;
			if (getClass().isAssignableFrom(other.getClass()))
			{	
				// "other" was cloned.
				
				if (FormatUtils.specified(this.sampleRate))
					resultCast.sampleRate = this.sampleRate;
				if (FormatUtils.specified(this.sampleSizeInBits))
					resultCast.sampleSizeInBits = this.sampleSizeInBits;
				if (FormatUtils.specified(this.channels))
					resultCast.channels = this.channels;
				if (FormatUtils.specified(this.endian))
					resultCast.endian = this.endian;
				if (FormatUtils.specified(this.signed))
					resultCast.signed = this.signed;
				if (FormatUtils.specified(this.frameSizeInBits))
					resultCast.frameSizeInBits = this.frameSizeInBits;
				if (FormatUtils.specified(this.frameRate))
					resultCast.frameRate = this.frameRate;
				
			}
			else if (other.getClass().isAssignableFrom(getClass()))
			{	// this was cloned
				
				if (!FormatUtils.specified(resultCast.sampleRate))
					resultCast.sampleRate = oCast.sampleRate;
				if (!FormatUtils.specified(resultCast.sampleSizeInBits))
					resultCast.sampleSizeInBits = oCast.sampleSizeInBits;
				if (!FormatUtils.specified(resultCast.channels))
					resultCast.channels = oCast.channels;
				if (!FormatUtils.specified(resultCast.endian))
					resultCast.endian = oCast.endian;
				if (!FormatUtils.specified(resultCast.signed))
					resultCast.signed = oCast.signed;
				if (!FormatUtils.specified(resultCast.frameSizeInBits))
					resultCast.frameSizeInBits = oCast.frameSizeInBits;
				if (!FormatUtils.specified(resultCast.frameRate))
					resultCast.frameRate = oCast.frameRate;
			
			}
		}
		
		if (getClass() == FormatUtils.audioFormatClass) FormatUtils.traceIntersects(this, other, result);
		
		return result;
	}



	
	public Object clone()
	{
		return new AudioFormat(encoding, sampleRate, sampleSizeInBits,
				channels, endian, signed, frameSizeInBits, frameRate, dataType);

	}
	
	protected void copy(Format f)
	{	
		super.copy(f);
		final AudioFormat oCast = (AudioFormat) f;	// it has to be a AudioFormat, or ClassCastException will be thrown.
		this.sampleRate = oCast.sampleRate;
		this.sampleSizeInBits = oCast.sampleSizeInBits;
		this.channels = oCast.channels;
		this.endian = oCast.endian;
		this.signed = oCast.signed;
		this.frameSizeInBits = oCast.frameSizeInBits;
		this.frameRate = oCast.frameRate;

	}


}
